package it.unibo.refolding.pico.batch;

import it.unibo.refolding.common.MyRuntimeException;
import it.unibo.refolding.pico.RetractionCurve;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;

public class NavigableMapRetractionCurveBatch extends RetractionCurveBatchBase implements
  RetractionCurveBatch {

  private final ConcurrentNavigableMap<Path, RetractionCurve> map;
  private static final Logger logger = Logger.getLogger(NavigableMapRetractionCurveBatch.class.getSimpleName());

  protected final Path retractionPlaylistPath;
  protected final Path filteredRetractionPlaylistPath;
  protected final Path peakCsvPath;
  protected final Path hookePlaylistPath;
  protected final Path filteredHookePlaylistPath;

  public NavigableMapRetractionCurveBatch(int iteration, Path retractionPlaylistPath, Path peakCsvPath,
    Path hookePlaylistPath, double clRangeStartM, double clRangeEndM, int wellSpacedAtEnd,
    int allowSkipped, Path filteredRetractionPlaylistPath, Path filteredHookePlaylistPath,
    ConcurrentNavigableMap<Path, RetractionCurve> map) {
    super(
      iteration, retractionPlaylistPath, peakCsvPath, hookePlaylistPath, clRangeStartM, clRangeEndM,
      wellSpacedAtEnd, allowSkipped, filteredRetractionPlaylistPath, filteredHookePlaylistPath);
    this.retractionPlaylistPath = retractionPlaylistPath;
    this.peakCsvPath = peakCsvPath;
    this.filteredRetractionPlaylistPath = filteredRetractionPlaylistPath;
    this.hookePlaylistPath = hookePlaylistPath;
    this.filteredHookePlaylistPath = filteredHookePlaylistPath;
    this.map = map;
  }

  @Override
  public void load() {
    map.clear();
    map.putAll(readRetractionPlaylist(retractionPlaylistPath));
  }

  @Override
  public int size() {
    return map.size();
  }

  @Override
  public boolean containsKey(Path key) {
    return map.containsKey(key);
  }

  @Override
  public RetractionCurve put(Path key, RetractionCurve value) {
    return map.put(key, value);
  }

  @Override
  public RetractionCurve get(Path key) {
    return map.get(key);
  }

  @Override
  public void write() {
    try {
      writeRetractionPlaylist(map, iteration, 0, Double.POSITIVE_INFINITY, 0, 0, retractionPlaylistPath);
      writePeakCsv(map, iteration, peakCsvPath);
      writeHookePlaylist(iteration, map, 0, Double.POSITIVE_INFINITY, 0, 0, hookePlaylistPath);
      writeRetractionPlaylist(
        map, iteration, clRangeStartM, clRangeEndM, wellSpacedAtEnd, allowSkipped,
        filteredRetractionPlaylistPath);
      writeHookePlaylist(
        iteration, map, clRangeStartM, clRangeEndM, wellSpacedAtEnd, allowSkipped, filteredHookePlaylistPath);
    } catch (IOException ex) {
      throw new MyRuntimeException(ex);
    }
  }

  @Override
  public Set<Path> keySet() {
    return map.keySet();
  }

  @Override
  public Set<Entry<Path, RetractionCurve>> entrySet() {
    return map.entrySet();
  }

  public static Map<Path, RetractionCurve> readRetractionPlaylist(Path path) {
    Map<Path, RetractionCurve> map = Maps.newTreeMap();
    if (Files.isRegularFile(path)) {
      try {
        try (BufferedReader reader = Files.newBufferedReader(path, Charsets.ISO_8859_1)) {
          for (String line = reader.readLine(); line != null; line = reader.readLine()) {
            if (!StringUtils.isEmpty(line)) {
              RetractionCurve rc = RetractionCurve.parseRepr(line);
              RetractionCurve old = map.put(Paths.get(rc.path), rc);
              Preconditions.checkState(old == null, "duplicate: %s", line);
            }
          }
        }
      } catch (IOException ex) {
        throw new MyRuntimeException(path.toString(), ex);
      }
    }
    return map;
  }

  static void writeRetractionPlaylist(
    Map<?, RetractionCurve> map, int iteration, double clRangeStartM, double clRangeEndM, int wellSpacedAtEnd,
    int allowSkipped, Path retractionPlaylistPath) throws IOException {
    if (!StringUtils.isEmpty(retractionPlaylistPath.toString())) {
      int i = 0;
      try (Writer retractionFw = Files.newBufferedWriter(
        retractionPlaylistPath, Charsets.ISO_8859_1)) {
        for (RetractionCurve value : map.values()) {
          boolean written = BatchUtil.writeIfNecessary(
            clRangeStartM, clRangeEndM, wellSpacedAtEnd, allowSkipped, value, iteration, retractionFw);
          if (written) {
            ++i;
          }
        }
      }
      logger.log(Level.INFO, "Wrote {0} elements into: {1}", new Object[] {
      i, retractionPlaylistPath});
    }
  }

  static void writePeakCsv(Map<?, RetractionCurve> map, int iteration, Path csvPath) throws IOException {
    if (!StringUtils.isEmpty(csvPath.toString())) {
      int i = 0;
      try (Writer retractionFw = Files.newBufferedWriter(csvPath, Charsets.ISO_8859_1)) {
        for (RetractionCurve value : map.values()) {
          BatchUtil.writePeakCsvEntry(i, value, iteration, retractionFw);
          ++i;
        }
      }
      logger.log(Level.INFO, "Wrote {0} elements into: {1}", new Object[] {i, csvPath});
    }
  }

  private static void writeHookePlaylist(
    int iteration, Map<?, RetractionCurve> map, double clRangeStartM, double clRangeEndM, int wellSpacedAtEnd,
    int allowSkipped, Path filteredHookePlaylistPath) throws IOException {
    if (!StringUtils.isEmpty(filteredHookePlaylistPath.toString())) {
      int i = 0;
      try (Writer hookeFw = Files.newBufferedWriter(filteredHookePlaylistPath, Charsets.ISO_8859_1)) {
        hookeFw.write(BatchUtil.HOOKE_HEADER);
        for (RetractionCurve value : map.values()) {
          boolean written;
          written = BatchUtil.writeHookeElement(
            clRangeStartM, clRangeEndM, wellSpacedAtEnd, allowSkipped, value, iteration, hookeFw);
          if (written) {
            ++i;
          }
        }
        hookeFw.write(BatchUtil.HOOKE_FOOTER);
      }
      logger.log(Level.INFO, "Wrote {0} elements into: {1}", new Object[] {
      i, filteredHookePlaylistPath});
    }
  }
}
